/*!
 * @author  Vladimir Kerimov
 * @file    field.h
 * @version 1.0
 * @brief   Data field
 * @date    2013
 */

/*
 *    ______   __
 *   /  ___/  /__/   __  ___      _____        _____     __    __     _____        __  ___      _____     __  ___
 *  |  |___    __   /  |/   \    / __  \      /  _  \   |  |  |  |   /  _  \      |  |/   \    /  _  \   |  |/   \
 *   \___  \  /  |  |   __   |  | /  __/     |  / \  |  |  |  |  |  |  / \  |     |   __   |  |  / \  |  |   __   |
 *  ____/  /  |  |  |  /  \  |  |   /___     |  \_/  |  |   \_|  |  |  \_/  |     |  /  \  |  |  \_/  |  |  /  \  |
 * /______/   |__/  |__|  |__/   \_____/      \___   |   \___/|__|   \___/|_|     |__|  |__|   \_____/   |__|  |__|
 *                                                |  |
 *                                                |__/
 *
 */

#pragma once

#include <qua/api.h>
#include <qua/cast.h>
#include <type_traits>
#include <cstdint>
#include <cstddef>

namespace qua
{
    class fixed;
    class text;
    class time;

    template< typename T >
    class opt;

    class QUAZAR_API field
    {
    public:
        class format;

        enum type
        {
            of_bool,
            of_int,
            of_float,
            of_fixed,
            of_text,
            of_time,
            of_bytes,
            of_unknown_type
        };

        static text type_text( type t );

        field( void* at, format const& fmt );

        ~field();

        template< typename T >
        field( void* at, format const& fmt, T val );

        type get_type() const;

        text get_name() const;

        format const& get_format() const;

        bool is_null() const;
        void clear();

        template< typename T >
        T get_value() const;

        template< typename T >
        opt<T> opt_value() const;

        template< typename T >
        void set_value( T src );

        size_t data_size() const;

        text to_text() const;

        template< field::type T >
        struct type_of;

    private:
        class data;

        template< typename T >
        class value;

        data* m_data;
    };

    class QUAZAR_API field::format
    {
    public:
        format( void* at, text const& name, type field_type );

        ~format();

        template< typename T >
        format( void* at, text const& name, T value );

        text get_name() const;

        type get_type() const;

        bool is_null_default() const;
        void clear_default();

        template< typename T >
        T get_default() const;

        template< typename T >
        void set_default( T val );

        template< typename T >
        opt<T> opt_default() const
        {
            return is_null_default() ? opt<T>() : opt<T>( get_default<T>() );
        }

        template< typename T >
        void set_default( opt<T> const& optval )
        {
            optval.is_null() ? clear_default() : set_default<T>( optval.get_value() );
        }

    public:
        class data;

        template< typename T >
        class value;

        data* m_data;
    };

    template<> struct field::type_of<field::of_bool > { typedef bool    type; };
    template<> struct field::type_of<field::of_int  > { typedef int64_t type; };
    template<> struct field::type_of<field::of_float> { typedef double  type; };
    template<> struct field::type_of<field::of_fixed> { typedef fixed   type; };
    template<> struct field::type_of<field::of_text > { typedef text    type; };
    template<> struct field::type_of<field::of_time > { typedef time    type; };

    // field template specifications

    template<> QUAZAR_API field::field( void* at, field::format const& format, field::type_of<field::of_bool >::type value );
    template<> QUAZAR_API field::field( void* at, field::format const& format, field::type_of<field::of_int  >::type value );
    template<> QUAZAR_API field::field( void* at, field::format const& format, field::type_of<field::of_float>::type value );
    template<> QUAZAR_API field::field( void* at, field::format const& format, field::type_of<field::of_fixed>::type value );
    template<> QUAZAR_API field::field( void* at, field::format const& format, field::type_of<field::of_text >::type value );
    template<> QUAZAR_API field::field( void* at, field::format const& format, field::type_of<field::of_time >::type value );

    template<> QUAZAR_API field::type_of<field::of_bool >::type  field::get_value< field::type_of<field::of_bool >::type >() const;
    template<> QUAZAR_API field::type_of<field::of_int  >::type  field::get_value< field::type_of<field::of_int  >::type >() const;
    template<> QUAZAR_API field::type_of<field::of_float>::type  field::get_value< field::type_of<field::of_float>::type >() const;
    template<> QUAZAR_API field::type_of<field::of_fixed>::type  field::get_value< field::type_of<field::of_fixed>::type >() const;
    template<> QUAZAR_API field::type_of<field::of_text >::type  field::get_value< field::type_of<field::of_text >::type >() const;
    template<> QUAZAR_API field::type_of<field::of_time >::type  field::get_value< field::type_of<field::of_time >::type >() const;

    QUAZAR_API void throw_value_invalid_type( const char* type_name );

    template< typename T >
    T field::get_value() const
    {
        if( std::is_integral<T>::value )
            return cast<T>( get_value< field::type_of<field::of_int>::type >() );
        else if( std::is_floating_point<T>::value )
            return cast<T>( get_value< field::type_of<field::of_float>::type >() );
        else
            throw_value_invalid_type( typeid(T).name() );
    }

    template<> QUAZAR_API void field::set_value( field::type_of<field::of_bool >::type value );
    template<> QUAZAR_API void field::set_value( field::type_of<field::of_int  >::type value );
    template<> QUAZAR_API void field::set_value( field::type_of<field::of_float>::type value );
    template<> QUAZAR_API void field::set_value( field::type_of<field::of_fixed>::type value );
    template<> QUAZAR_API void field::set_value( field::type_of<field::of_text >::type value );
    template<> QUAZAR_API void field::set_value( field::type_of<field::of_time >::type value );

    template< typename T >
    void field::set_value( T val )
    {
        if( std::is_integral<T>::value )
            set_value( cast< field::type_of<field::of_int>::type >( val ) );
        else if( std::is_floating_point<T>::value )
            set_value( cast< field::type_of<field::of_float>::type >( val ) );
        else
            throw_value_invalid_type( typeid(T).name() );
    }

    // field format template specification

    template<> QUAZAR_API field::format::format( void* at, text const& name, field::type_of<field::of_bool >::type value );
    template<> QUAZAR_API field::format::format( void* at, text const& name, field::type_of<field::of_int  >::type value );
    template<> QUAZAR_API field::format::format( void* at, text const& name, field::type_of<field::of_float>::type value );
    template<> QUAZAR_API field::format::format( void* at, text const& name, field::type_of<field::of_fixed>::type value );
    template<> QUAZAR_API field::format::format( void* at, text const& name, field::type_of<field::of_text >::type value );
    template<> QUAZAR_API field::format::format( void* at, text const& name, field::type_of<field::of_time >::type value );

    template<> QUAZAR_API field::type_of<field::of_bool >::type  field::format::get_default< field::type_of<field::of_bool >::type >() const;
    template<> QUAZAR_API field::type_of<field::of_int  >::type  field::format::get_default< field::type_of<field::of_int  >::type >() const;
    template<> QUAZAR_API field::type_of<field::of_float>::type  field::format::get_default< field::type_of<field::of_float>::type >() const;
    template<> QUAZAR_API field::type_of<field::of_fixed>::type  field::format::get_default< field::type_of<field::of_fixed>::type >() const;
    template<> QUAZAR_API field::type_of<field::of_text >::type  field::format::get_default< field::type_of<field::of_text >::type >() const;
    template<> QUAZAR_API field::type_of<field::of_time >::type  field::format::get_default< field::type_of<field::of_time >::type >() const;

    template< typename T >
    T field::format::get_default() const
    {
        if( std::is_integral<T>::value )
            return cast<T>( get_default< field::type_of<field::of_int>::type >() );
        else if( std::is_floating_point<T>::value )
            return cast<T>( get_default< field::type_of<field::of_float>::type >() );
        else
            throw_value_invalid_type( typeid(T).name() );
    }

    template<> QUAZAR_API void field::format::set_default( field::type_of<field::of_bool >::type value );
    template<> QUAZAR_API void field::format::set_default( field::type_of<field::of_int  >::type value );
    template<> QUAZAR_API void field::format::set_default( field::type_of<field::of_float>::type value );
    template<> QUAZAR_API void field::format::set_default( field::type_of<field::of_fixed>::type value );
    template<> QUAZAR_API void field::format::set_default( field::type_of<field::of_text >::type value );
    template<> QUAZAR_API void field::format::set_default( field::type_of<field::of_time >::type value );

    template< typename T >
    void field::format::set_default( T value )
    {
        if( std::is_integral<T>::value )
            set_default( cast< type_of< of_int >::type >( value ) );
        else if( std::is_floating_point<T>::value )
            set_default( cast< type_of< of_float >::type >( value ) );
        else
            throw_value_invalid_type( typeid(T).name() );
    }
}
